﻿using System;
using System.Collections.Generic;
using System.Common.Highliter;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace System.Controls.Highliter
{
  public class RtfBuilder
  {

    #region ...public methods...

    public string GetRtfContent( string text, ISqlFormatProvider prov )
    {
      fStringBuilder.Clear();
      Append( @"{\rtf1\fbidis\ansi\ansicpg1250\deff0\deflang1038" );

      CreateFontTable();
      CreateColorTable();

      Append( @"\par \pard\plain " );
      SetDefaultSettings();

      var source = text.Replace( "\r\n", "\r" ).Replace( "\\", "\\\\" ).Replace( "{", "\\{" ).Replace( "}", "\\}" );

      try
      {

        prov.SetText( source );

        while ( !prov.Eof )
        {
          prov.Next();

          HighlightStyle style = null;
          if ( !fHighlights.TryGetValue( prov.TokenId, out style ) )
            style = HighlightStyle.GetDefault();

          switch ( prov.TokenId )
          {
          case TokenType.CRLF:
            AppendLine();
            break;

          case TokenType.Comment:
            CreateComment( prov.Token, style );
            break;

          default:
            Append( '{' );
            SetStyle( style );
            Append( prov.Token );
            Append( '}' );
            break;
          }
        }

        Append( '}' );

        return fStringBuilder.ToString();
      }
      finally
      {
        prov.Dispose();
      }
    }

    public RtfBuilder Append( object value )
    {
      fStringBuilder.Append( value );
      return this;
    }

    public RtfBuilder Append( string value )
    {
      if ( string.IsNullOrEmpty( value ) )
        return this;

      fStringBuilder.Append( value );
      return this;
    }

    public RtfBuilder Append( char value )
    {
      fStringBuilder.Append( value );
      return this;
    }

    public RtfBuilder AppendLine( string value )
    {
      Append( value );
      return AppendLine();
    }

    #endregion

    #region ...private methods...

    private RtfBuilder AppendLine()
    {
      return Append( "\n\\par \\pard\\plain" );
    }

    private void CreateComment( string comment, HighlightStyle style )
    {
      if ( string.IsNullOrEmpty( comment ) )
        return;

      var lines = comment.Split( '\r' );

      foreach ( var line in lines )
      {
        Append( '{' );
        SetStyle( style );
        Append( line );
        Append( '}' );
        AppendLine();
      }
    }

    private void EndTags()
    {
      Append( ' ' );
    }

    private void SetBackground( Color color )
    {
      var index = fColors.IndexOf( color );

      Append( @"\cb" ).Append( index + 1 );
    }

    private void SetDefaultSettings()
    {
      SetStyle( HighlightStyle.GetDefault(), false );
    }

    private void SetFont( string fontName )
    {
      var index = fFonts.IndexOf( fontName );
      if ( index == -1 )
        index = 0;

      Append( @"\f" ).Append( index );
    }

    private void SetFontSize( double size )
    {
      Append( @"\fs" ).Append( (int) ( size * 2 ) );
    }

    private void SetFontStyle( FontStyle fontStyle )
    {
      Append( fontStyle == FontStyles.Italic ? @"\i" : string.Empty );
    }

    private void SetFontWeight( FontWeight fontWeight )
    {
      Append( fontWeight == FontWeights.Bold ? @"\b" : string.Empty );
    }

    private void SetForeground( Color color )
    {
      var index = fColors.IndexOf( color );

      Append( @"\cf" ).Append( index + 1 );
    }

    private void SetStyle( HighlightStyle style, bool endTag = true )
    {
      SetBackground( style.Background );
      SetForeground( style.Foreground );
      SetFont( style.FontName );
      SetFontSize( style.FontSize );
      SetFontWeight( style.FontWeight );
      SetFontStyle( style.FontStyle );

      if ( endTag )
        EndTags();
    }

    #endregion

    #region ... fields ...

    private readonly List<Color> fColors = new List<Color>();
    private readonly List<string> fFonts = new List<string>();
    private readonly HighlightCollection fHighlights = new HighlightCollection();
    private readonly StringBuilder fStringBuilder = new StringBuilder();

    #endregion

    #region ... rtf style tables ...

    private void AddColor( Color color )
    {
      if ( fColors.Contains( color ) )
        return;

      Append( @"\red" ).Append( color.R ).
                        Append( @"\green" ).Append( color.G ).
                        Append( @"\blue" ).Append( color.B ).
                        Append( ";" );

      fColors.Add( color );
    }

    private void AddFont( string fontName )
    {
      if ( fFonts.Contains( fontName ) )
        return;

      Append( @"{\f" ).Append( fFonts.Count ).Append( @"\fnil\fcharset0 " ).Append( fontName ).Append( ";}" );
      fFonts.Add( fontName );
    }

    private void CreateColorTable()
    {
      Append( @"{\colortbl;" );
      AddColor( Colors.Black );
      AddColor( Colors.White );

      AddColor( HighlightStyle.GetDefault().Background );
      AddColor( HighlightStyle.GetDefault().Foreground );

      foreach ( var highlightDescriptor in fHighlights )
      {
        AddColor( highlightDescriptor.Background );
        AddColor( highlightDescriptor.Foreground );
      }

      Append( "}\n" );
    }

    private void CreateFontTable()
    {
      Append( @"{\fonttbl" );

      AddFont( HighlightStyle.GetDefault().FontName );

      foreach ( var highlightDescriptor in fHighlights )
        AddFont( highlightDescriptor.FontName );

      Append( "}\n" );
    }

    #endregion
  }
}
